#include "at24cxx.h"

/**
型号	容量(bits)	页数	页字节数	寻址位数
AT24C01	    1K	    16	    8	        8
AT24C02	    2K	    32	    8	        8
AT24C04	    4K	    32	    16	        9
AT24C08	    8K	    64	    16	        10
AT24C16	    16K	    128	    16	        11
AT24C32	    32K	    128	    32	        12
AT24C64	    64K	    256	    32	        13
AT24C128	128K	256	    64	        14
AT24C256	256K	512	    64	        15
AT24C512	512K	512	    128	        16
*/

#define WR_ADDR(a) ((a) << 1)
#define RD_ADDR(a) (((a) << 1) | 1)

/**
 * @brief 初始化at24cxx_t设备数据结构体
 * @param p Pointer to a at24cxx_t structure that contains
 *               the configuration information for the specified EEPROM module.
 * @param ct EEPROM 型号
 * @param dev 设备地址 (7位)
 * @param sl 速度等级
 * @param pdev I2C设备指针
 * @return 0
 */
int32_t at24cxx_init(at24cxx_t *p, at24cxx_chiptype_t ct, uint8_t dev, at24cxx_speedlevel_t sl, iic_t *pdev)
{
    p->eetype = ct;
    switch (p->eetype)
    {
    case AT24C01:
        p->pagesize = 8;
        p->pagenum = 16;
        break;
    case AT24C02:
        p->pagesize = 8;
        p->pagenum = 32;
        break;
    case AT24C04:
        p->pagesize = 16;
        p->pagenum = 32;
        break;
    case AT24C08:
        p->pagesize = 16;
        p->pagenum = 64;
        break;
    case AT24C16:
        p->pagesize = 16;
        p->pagenum = 128;
        break;
    case AT24C32:
        p->pagesize = 32;
        p->pagenum = 128;
        break;
    case AT24C64:
        p->pagesize = 32;
        p->pagenum = 256;
        break;
    case AT24C128:
        p->pagesize = 64;
        p->pagenum = 256;
        break;
    case AT24C256:
        p->pagesize = 64;
        p->pagenum = 512;
        break;
    case AT24C512:
        p->pagesize = 128;
        p->pagenum = 512;
        break;
    default:
        return -1;
    }
    p->eedevaddr = dev;
    p->eespdlvl = sl;
    p->pi2c = pdev;
    p->wrdlyms = 10;
    iic_init(p->pi2c);
    return 0;
}

/**
 * @brief Read a data in blocking mode.
 * @param p Pointer to a at24cxx_t structure that contains
 *               the configuration information for the specified EEPROM module.
 * @param readaddr The EEPROM memory begin address
 * @param pbuffer Pointer to data buffer (u8 data elements).
 * @return  0
 */
int32_t at24cxx_readonebyte(at24cxx_t *p, uint16_t readaddr, uint8_t *pbuffer)
{
    uint8_t temp = 0;
    iic_start(p->pi2c);
    if (p->eetype > AT24C16)
    {
        iic_send_byte(p->pi2c, WR_ADDR(p->eedevaddr)); // 发送写命令
        iic_wait_ack(p->pi2c);
        iic_send_byte(p->pi2c, readaddr >> 8); // 发送高地址
    }
    else
        iic_send_byte(p->pi2c, WR_ADDR(p->eedevaddr) + ((readaddr >> 8) << 1)); // 发送器件地址0xa0,写数据
    iic_wait_ack(p->pi2c);
    iic_send_byte(p->pi2c, readaddr & 0xFF); // 发送低地址
    iic_wait_ack(p->pi2c);
    iic_start(p->pi2c);
    iic_send_byte(p->pi2c, RD_ADDR(p->eedevaddr)); // 进入接收模式
    iic_wait_ack(p->pi2c);
    iic_read_byte(p->pi2c, &temp, 0);
    iic_stop(p->pi2c); // 产生一个停止条件
    *pbuffer = temp;
    return 0;
}

/**
 * @brief Read an amount of data in blocking mode.
 * @param p Pointer to a at24cxx_t structure that contains
 *               the configuration information for the specified EEPROM module.
 * @param readaddr The EEPROM memory begin address
 * @param pbuffer Pointer to data buffer (u8 data elements).
 * @param numtoread Amount of data elements (u8) to be sent
 * @return  Amount of read operation successed
 */
int32_t at24cxx_read(at24cxx_t *p, uint16_t readaddr, uint8_t *pbuffer, uint32_t numtoread)
{
    uint8_t *pdst = pbuffer;
    iic_start(p->pi2c);
    if (p->eetype > AT24C16)
    {
        iic_send_byte(p->pi2c, WR_ADDR(p->eedevaddr)); // 发送写命令
        iic_wait_ack(p->pi2c);
        iic_send_byte(p->pi2c, readaddr >> 8); // 发送高地址
    }
    else
        iic_send_byte(p->pi2c, WR_ADDR(p->eedevaddr) + ((readaddr >> 8) << 1)); // 发送器件地址0xa0,写数据
    iic_wait_ack(p->pi2c);
    iic_send_byte(p->pi2c, readaddr & 0xFF); // 发送低地址
    iic_wait_ack(p->pi2c);
    iic_start(p->pi2c);
    iic_send_byte(p->pi2c, RD_ADDR(p->eedevaddr)); // 进入接收模式
    iic_wait_ack(p->pi2c);
    while (numtoread > 1)
    {
        iic_read_byte(p->pi2c, pdst++, 1);
        numtoread--;
    }
    iic_read_byte(p->pi2c, pdst, 0);
    iic_stop(p->pi2c); // 产生一个停止条件
    return numtoread;
}

/**
 * @brief Write a byte in blocking mode
 * @param p Pointer to a at24cxx_t structure that contains
 *               the configuration information for the specified EEPROM module.
 * @param writeaddr The EEPROM memory begin address
 * @param datatowrite u8 data elements.
 * @return 1 write succeed else failed
 */
int32_t at24cxx_writeonebyte(at24cxx_t *p, uint16_t writeaddr, uint8_t datatowrite)
{
    int32_t ret;
    iic_start(p->pi2c);
    if (p->eetype > AT24C16)
    {
        iic_send_byte(p->pi2c, WR_ADDR(p->eedevaddr)); // 发送写命令
        iic_wait_ack(p->pi2c);
        iic_send_byte(p->pi2c, writeaddr >> 8); // 发送高地址
    }
    else
        iic_send_byte(p->pi2c, WR_ADDR(p->eedevaddr) + ((writeaddr >> 8) << 1)); // 发送器件地址0xa0,写数据
    iic_wait_ack(p->pi2c);
    iic_send_byte(p->pi2c, writeaddr & 0xFF); // 发送低地址
    iic_wait_ack(p->pi2c);
    iic_send_byte(p->pi2c, datatowrite); // 发送字节
    ret = iic_wait_ack(p->pi2c);
    iic_stop(p->pi2c); // 产生一个停止条件
    return (0 == ret) ? 1 : -1;
}

/**
 * @brief Write an amount of data in blocking mode
 * @param p Pointer to a at24cxx_t structure that contains
 *               the configuration information for the specified EEPROM module.
 * @param writeaddr The EEPROM memory begin address
 * @param pbuf Pointer to data buffer (u8 data elements).
 * @param datatowrite Amount of data elements (u8) to be write.
 * @return 0
 */
static int32_t at24cxx_writelenbyte(at24cxx_t *p, uint16_t writeaddr, uint8_t *pbuf, uint8_t datatowrite)
{
    uint8_t *psrc = pbuf;
    iic_start(p->pi2c);
    if (p->eetype > AT24C16)
    {
        iic_send_byte(p->pi2c, WR_ADDR(p->eedevaddr)); // 发送写命令
        iic_wait_ack(p->pi2c);
        iic_send_byte(p->pi2c, writeaddr >> 8); // 发送高地址
    }
    else
        iic_send_byte(p->pi2c, WR_ADDR(p->eedevaddr) + ((writeaddr >> 8) << 1)); // 发送器件地址0xa0,写数据
    iic_wait_ack(p->pi2c);
    iic_send_byte(p->pi2c, writeaddr & 0xFF); // 发送低地址
    iic_wait_ack(p->pi2c);
    while (datatowrite--)
    {
        iic_send_byte(p->pi2c, *psrc++); // 发送字节
        iic_wait_ack(p->pi2c);
    }
    iic_stop(p->pi2c); // 产生一个停止条件
    return 0;
}
/**
 * @brief 检查at24cxx是否正常
 * @note 这里用了24xx的最后一个地址来存储标志字 @EEPROM_LAST_ADDR_CHECK_DATA.
 * @param p
 * @return 返回-1:检测失败
 *         返回 0:检测成功
 */
int32_t at24cxx_check(at24cxx_t *p)
{
    uint8_t temp;
    // 避免每次开机都写at24cxx
    at24cxx_readonebyte(p, p->eetype, &temp);
    if (temp == EEPROM_LAST_ADDR_CHECK_DATA)
        return 0;
    else // 排除第一次初始化的情况
    {
        at24cxx_writeonebyte(p, p->eetype, EEPROM_LAST_ADDR_CHECK_DATA);
        at24cxx_readonebyte(p, p->eetype, &temp);
        if (temp == EEPROM_LAST_ADDR_CHECK_DATA)
            return 0;
    }
    return -1;
}

/**
 * @brief Write an amount of data in blocking mode.
 * @attention 跨页写操作工况，存在阻塞写等待约10ms，精度取决于IIC设备io驱动中的dlyus函数精度。
 * @param p Pointer to a at24cxx_t structure that contains
 *               the configuration information for the specified EEPROM module.
 * @param writeaddr The EEPROM memory begin address
 * @param pbuffer Pointer to data buffer (u8 data elements).
 * @param numtowrite Amount of data elements (u8) to be sent
 * @return  Amount of write operation successed
 */
int32_t at24cxx_write(at24cxx_t *p, uint16_t writeaddr, uint8_t *pbuffer, uint32_t numtowrite)
{
    int32_t ret;
    if (1 == numtowrite)
    {
        ret = at24cxx_writeonebyte(p, writeaddr, pbuffer[0]);
        return ret;
    }
    else
    {
        /* 计算页号 */
        uint16_t secoff = writeaddr % p->pagesize;
        /* 计算页内自写起始地址可写字节数 */
        uint16_t secremain = p->pagesize - secoff;

        /* 可在当前页内写完, */
        if (numtowrite <= secremain)
        {
            at24cxx_writelenbyte(p, writeaddr, pbuffer, numtowrite);
            ret = numtowrite;
        }
        else
        {
            /* 待写入源数据指针 */
            uint8_t *psrc = pbuffer;
            ret = 0;
            for (;;)
            {
                at24cxx_writelenbyte(p, writeaddr, psrc, secremain);
                ret += secremain;
                /*完成判定*/
                if (numtowrite == secremain)
                {
                    break;
                }
                else
                {
                    /*源数据指针偏移*/
                    psrc += secremain;
                    /*写入地址偏移*/
                    writeaddr += secremain;
                    /*计算待写入字节数*/
                    numtowrite -= secremain;
                    /*够整页写整页*/
                    if (numtowrite >= p->pagesize)
                        secremain = p->pagesize;
                    /*不足页，只写剩余*/
                    else
                        secremain = numtowrite;
                }
                p->pi2c->io.dlyus(p->wrdlyms * 5000);
            }
        }
        return ret;
    }
}
